package com.jdrx.sw.sewage.service;

import com.google.common.collect.Lists;
import com.jdrx.platform.commons.rest.beans.vo.ResponseVO;
import com.jdrx.platform.commons.rest.beans.vo.ResposeVO;
import com.jdrx.platform.commons.rest.exception.BizException;
import com.jdrx.platform.commons.rest.factory.ResponseFactory2;
import com.jdrx.platform.dubbo.common.beans.DubboRpcBean;
import com.jdrx.platform.dubbo.common.service.IDubboRpcService;
import com.jdrx.sw.sewage.beans.dto.JCWaterDTO;
import com.jdrx.sw.sewage.beans.dto.MtLogAddDTO;
import com.jdrx.sw.sewage.beans.dto.StationIdDTO;
import com.jdrx.sw.sewage.beans.dto.log.AddLogDTO;
import com.jdrx.sw.sewage.beans.entity.StationPO;
import com.jdrx.sw.sewage.beans.vo.*;
import com.jdrx.sw.sewage.common.constant.Constant;
import com.jdrx.sw.sewage.common.enums.EAlarmStatus;
import com.jdrx.sw.sewage.common.enums.EControllableDevType;
import com.jdrx.sw.sewage.common.enums.EProcShow;
import com.jdrx.sw.sewage.common.util.Asserts;
import com.jdrx.sw.sewage.dao.GatherDevDAO;
import com.jdrx.sw.sewage.dao.GatherDevPointDAO;
import com.jdrx.sw.sewage.remote.TenantLogRemote;
import com.jdrx.sw.sewage.service.base.BaseServiceImpl;
import com.jdrx.sw.sewage.service.common.CommandService;
import com.jdrx.sw.sewage.service.common.RedisService;
import com.jdrx.sw.sewage.service.common.SequenceDefineServiceImpl;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

import static com.jdrx.sw.sewage.common.constant.Constant.*;

@Service
public class GatherDevPoiServiceImpl extends BaseServiceImpl {

    public static final Logger logger = LoggerFactory.getLogger(GatherDevPoiServiceImpl.class);
    @Reference(group = "${group.app.name.iot-group-name}", check = false, timeout = 10000)
    protected IDubboRpcService dubboRpcService;
    @Autowired
    private StationServiceImpl stationServiceImpl;
    @Autowired
    private GatherDevPointDAO gatherDevPointDAO;
    @Autowired
    private RedisService redisService;
    @Autowired
    private TenantLogRemote logRemote;
    @Autowired
    private LogService logService;
    @Autowired
    private SequenceDefineServiceImpl sequenceDefineServiceImpl;
    @Autowired
    private RocketMQTemplate rocketMQTemplate;
    @Autowired
    private GatherDevDAO gatherDevDAO;
    @Autowired
    private CommandService commandService;
    @Value("${aioFlag}")
    private String aioFlag;

    /**
     * 根据站点id查询实时数据
     *
     * @param stationId
     * @return
     * @throws BizException
     */
    public List<GroupDeviceDisplayVO> findDisplayVOByStationId(Long stationId, String dataNode) throws BizException {
        Asserts.notNull(stationId, "站点id不能为空");
        if (!StringUtils.isEmpty(dataNode)) dataNode = getSQLPerfix(dataNode);
        //厂站和工艺单元
        List<GroupDeviceDisplayVO> groupDeviceDisplayVOS = stationServiceImpl.selectProcessunitByStationId(stationId, dataNode, null);
        if (null == groupDeviceDisplayVOS || groupDeviceDisplayVOS.size() == 0) return groupDeviceDisplayVOS;

        List<DeviceDateDisplayVO> deviceDateDisplayVOs = stationServiceImpl.selectPoinValuesByStationId(
                stationId,
                dataNode,
                EControllableDevType.S.getKey(),
                EControllableDevType.P.getKey(),
                EProcShow.PROC_SHOW.getKey());
        pointValueEnclosure(deviceDateDisplayVOs);

        //把DeviceDateDisplayVOs放到对应的组里面去
        Map<String, List<DeviceDateDisplayVO>> groupBy = deviceDateDisplayVOs.stream().collect(Collectors.groupingBy(DeviceDateDisplayVO::getProcessunitValue));
        groupDeviceDisplayVOS.forEach(item -> {
            List<DeviceDateDisplayVO> deviceDateDisplayVOS = groupBy.get(item.getProcessunitValue());
            if (null != deviceDateDisplayVOS && deviceDateDisplayVOS.size() > 0)
                item.setDisplayVOList(deviceDateDisplayVOS);
            Integer alarm = gatherDevPointDAO.selectAlarm(EAlarmStatus.COMPLETED.getKey(), item.getProcessunitId());
            if (alarm > 0) item.setAlarm(TRUE);
        });
        return groupDeviceDisplayVOS;
    }

    public List<DeviceDateDisplayVO> findDisplayVOByStationIdAndProcessunitId(Long stationId, String dataNode, Long processunitId) throws BizException {
        Asserts.notNull(stationId, "站点id不能为空");
        Asserts.notNull(processunitId, "工艺单元id不能为空");
        if (!StringUtils.isEmpty(dataNode)) dataNode = getSQLPerfix(dataNode);

        List<GroupDeviceDisplayVO> groupDeviceDisplayVOS = stationServiceImpl.selectProcessunitByStationId(stationId, dataNode, processunitId);
        if (null == groupDeviceDisplayVOS || groupDeviceDisplayVOS.size() == 0) return Lists.newArrayList();

        List<DeviceDateDisplayVO> deviceDateDisplayVOs = stationServiceImpl.selectPoinValuesByStationIdAndpId(
                stationId,
                processunitId,
                dataNode,
                EControllableDevType.S.getKey(),
                EControllableDevType.P.getKey());
        pointValueEnclosure(deviceDateDisplayVOs);
        return deviceDateDisplayVOs;
    }


    public List<DeviceDateDisplayVO> findDisplayByProIdAndDevId(Long devId, Long processunitId) throws BizException {
        Asserts.notNull(devId, "设备id不能为空");
        Asserts.notNull(processunitId, "工艺单元id不能为空");
        List<DeviceDateDisplayVO> deviceDateDisplayVOs = gatherDevDAO.findDisplayByProIdAndDevId(
                devId,
                processunitId);
        pointValueEnclosure(deviceDateDisplayVOs);
        return deviceDateDisplayVOs;
    }

    /**
     * {
     * "sn":"300220101246",
     * "tid":"12",
     * "time":"2021-05-08 16:50:00",
     * "executeMethod":"SET_PARAMS_VALUE_FANYI",
     * "params":{
     * "name":"DO 整型",
     * "value":"10000"
     * }
     * }
     */
    @Transactional(rollbackFor = Exception.class)
    public ResponseVO updateByVO(GatherParVO gatherParVO, String tenantId, String uid) throws BizException, InterruptedException {
        if (null == gatherParVO) return ResponseFactory2.err("设备命令对象为空");
        //查到厂站&工艺单元校验
        CommandDevVO commandDevVO = stationServiceImpl.selectPoinValuesByStationIdAndpIdAndgDid(gatherParVO.getStationId(), gatherParVO.getProcessunitId(), gatherParVO.getGatherDevPointId());
        if (null == commandDevVO) return ResponseFactory2.err("站点下工艺单元下设备的该设备点位不存在，不处理命令下发");
        String iotGatewayNum = commandDevVO.getIotGatewayNum();
        List<String> list = Arrays.asList(commandDevVO.getStationIotGatewayNums().split(","));
        if (!list.contains(iotGatewayNum)) return ResponseFactory2.err("物联网网关不正确，不处理下发命令，请联系运营人员修改网关！");

        String gatherDevPointValue = commandDevVO.getGatherDevPointValue();

        //调用物联网远程服务
        Map<String, Object> map = new HashMap<>();
        map.put("sn", iotGatewayNum);
        long tid = sequenceDefineServiceImpl.increment(DEVICE_COMMAND_NUM, null);
        map.put("tid", tid);
        map.put("time", new Date());
        map.put("executeMethod", "SET_PARAMS_VALUE_FANYI");

        Map<String, Object> par = new HashMap<>();
        par.put("name", commandDevVO.getGatherDevPointValue());
        par.put("value", gatherParVO.getVal());
        map.put("params", par);

        //发送mq，然后等结果
        rocketMQTemplate.convertAndSend("iot-fanyi-command-put-smart", map);
//        if (!) Asserts.check(false, "设备命令发送mq失败！");
        String result = commandService.waitCommandResult(tid);
        if (!TRUE_STR.equals(result)) return ResponseFactory2.err(result);

        //在设定值到数据库
        update(gatherParVO, iotGatewayNum, gatherDevPointValue);

        //记录命令日志
        try {
            String realName = redisService.getMapField(Constant.REDIS_USER + tenantId + ":" + uid, "realName");
            String tenantName = redisService.getMapField(Constant.REDIS_USER + tenantId + ":" + uid, "tenantName");
            String description = "";
    		// 设备控制类记录
    		if(StringUtils.equals(commandDevVO.getControlType(), EControllableDevType.C.getKey())) {
    			if(StringUtils.contains(commandDevVO.getGatherDevPointValue(), "KG")) {
        			String kg = StringUtils.equals("0", gatherParVO.getVal()) ? "关闭" : "开启";
        			description = kg + "【" + commandDevVO.getProcessunitName() + "-" + commandDevVO.getDeviceName() + "】";
        		} else {
        			String ms = StringUtils.equals("0", gatherParVO.getVal()) ? "手动" : "自动";
        			description = "将【" + commandDevVO.getProcessunitName() + "-"
    					 	+ commandDevVO.getDeviceName()
    					 	+ "-控制模式】修改为【"
    					 	+ ms + "】";
        		}
    		} else {// 参数设置类记录
    			description = "将【" + commandDevVO.getProcessunitName() + "-"
					 	+ commandDevVO.getDeviceName()
					 	+ "-"+ commandDevVO.getGatherDevPointName() +"】修改为【"
					 	+ gatherParVO.getVal() + "】";
    		}
    		
    		if(StringUtils.isBlank(aioFlag)) {
    			logRemote.add(MtLogAddDTO.builder()
   					 .userId(uid == null ? null : Long.valueOf(uid))
   					 .user(realName)
   					 .tenant(tenantName)
   					 .tenantId(tenantId == null ? null : Long.valueOf(tenantId))
   					 .stationId(commandDevVO.getStationId())
   					 .station(commandDevVO.getStationName())
   					 .event(EControllableDevType.getValue(commandDevVO.getControlType()))
   					 .terminal(gatherParVO.getLogTerminal())
   					 .description(description)
   					 .build());
    		}
			
			logService.add(AddLogDTO.builder()
					 .userId(uid == null ? null : Long.valueOf(uid))
					 .user(realName)
					 .tenant(tenantName)
					 .tenantId(tenantId == null ? null : Long.valueOf(tenantId))
                    .stationId(commandDevVO.getStationId())
                    .station(commandDevVO.getStationName())
                    .event(EControllableDevType.getValue(commandDevVO.getControlType()))
                    .terminal(gatherParVO.getLogTerminal())
                    .description(description)
                    .build());
        } catch (Exception e) {
            logger.error("日志记录失败", e);
        }
        return ResponseFactory2.ok();
    }

    public void command(String value) throws BizException, InterruptedException {
        //调用物联网远程服务
        Map<String, Object> map = new HashMap<>();
        map.put("sn", "300220101246");
        long tid = sequenceDefineServiceImpl.increment(DEVICE_COMMAND_NUM, null);
        map.put("tid", tid);
        map.put("time", new Date());
        map.put("executeMethod", "SET_PARAMS_VALUE_FANYI");

        Map<String, Object> par = new HashMap<>();
        par.put("name", "JDJ_FCXT1_PL_T");
        par.put("value", value);
        map.put("params", par);

        //发送mq，然后等结果
        rocketMQTemplate.convertAndSend("iot-fanyi-command-put", map);
        String result = commandService.waitCommandResult(tid);
        if (!TRUE_STR.equals(result)) Asserts.check(false, result);
    }

    /**
     * 更新命令对象
     *
     * @param gatherParVO
     */
    @Transactional(rollbackFor = Exception.class)
    public void update(GatherParVO gatherParVO, String iotGatewayNum, String gatherDevPointValue) throws BizException {
        try {
            gatherDevPointDAO.updateByGatherDevPoiId(gatherParVO.getGatherDevPointId(), Double.valueOf(gatherParVO.getVal()));
            //更新redis或者删除redis
            String key = DEVICE_KEY + iotGatewayNum + "_" + gatherDevPointValue;
            Map<String, Object> map = redisService.getMap(key);
            if (MapUtils.isEmpty(map)) return;
            map.put("val", gatherParVO.getVal());
            redisService.setMap(key, map, null);
        } catch (Exception e) {
            logger.error("更新失败", e);
            throw new BizException("更新失败");
        }
    }

    private void pointValueEnclosure(List<DeviceDateDisplayVO> deviceDateDisplayVOs) {
        if (null == deviceDateDisplayVOs || deviceDateDisplayVOs.size() <= 0) return;

//        String iotGatewayNum = DEVICE_KEY + deviceDateDisplayVOs.get(0).getIotGatewayNum() + "_";
        for (DeviceDateDisplayVO item : deviceDateDisplayVOs) {
            //去redis拿到实时值
            String gatherDevPointValue = item.getGatherDevPointValue();
            Map<String, Object> map = redisService.getMap(DEVICE_KEY + item.getIotGatewayNum() + "_" + gatherDevPointValue);
            if (MapUtils.isEmpty(map)) continue;

            Double val = MapUtils.getDouble(map, "val");
            item.setVal(val);
            item.setControlValue(gatherDevPointValue.split("_")[2]);
        }
    }

    /**
     * @param rpcBean 远程参数
     * @return
     */
    public ResposeVO callRemoteService(DubboRpcBean rpcBean) throws BizException {
        ResposeVO resposeVO;
        try {
            resposeVO = (ResposeVO) dubboRpcService.callback(rpcBean);
        } catch (Exception e) {
            logger.error("调用物联网接口失败", e);
            throw new BizException("调用物联网接口失败");
        }
        return resposeVO;
    }


    public List<GroupDeviceDisplayVO> findParSetByStationId(Long stationId) throws BizException {
        Asserts.notNull(stationId, "站点id不能为空");
        //厂站和工艺单元
        List<GroupDeviceDisplayVO> groupDeviceDisplayVOS = stationServiceImpl.selectProcessunitByStationId(stationId, null, null);
        if (null == groupDeviceDisplayVOS || groupDeviceDisplayVOS.size() == 0) return Lists.newArrayList();

        //方案二 排除参数设置点位数据
        List<DeviceDateDisplayVO> deviceDateDisplayVOs = stationServiceImpl.selectPoinPasByStationId(stationId, EControllableDevType.S.getKey());
        if (null == deviceDateDisplayVOs || deviceDateDisplayVOs.size() <= 0) return new ArrayList<>();

        //把DeviceDateDisplayVOs放到对应的组里面去
        List<GroupDeviceDisplayVO> newGroupDeviceDisplayVOS = new ArrayList<>();
        Map<String, List<DeviceDateDisplayVO>> groupBy = deviceDateDisplayVOs.stream().collect(Collectors.groupingBy(DeviceDateDisplayVO::getProcessunitValue));
        groupDeviceDisplayVOS.forEach(item -> {
            List<DeviceDateDisplayVO> deviceDateDisplayVOS = groupBy.get(item.getProcessunitValue());
            if (null != deviceDateDisplayVOS && deviceDateDisplayVOS.size() > 0) {
                item.setDisplayVOList(deviceDateDisplayVOS);
                newGroupDeviceDisplayVOS.add(item);
            }
        });
        return newGroupDeviceDisplayVOS;
    }

    public List<GroupDeviceDisplayVO> findPlcByStationId(Long stationId) throws BizException {
        Asserts.notNull(stationId, "站点id不能为空");
        //厂站和工艺单元
        List<GroupDeviceDisplayVO> groupDeviceDisplayVOS = stationServiceImpl.selectProcessunitByStationId(stationId, null, null);
        if (null == groupDeviceDisplayVOS || groupDeviceDisplayVOS.size() == 0) return Lists.newArrayList();

        //方案二 排除参数设置点位数据
        List<DeviceDateDisplayVO> deviceDateDisplayVOs = stationServiceImpl.selectPoinPasByStationId(stationId, EControllableDevType.P.getKey());
        if (null == deviceDateDisplayVOs || deviceDateDisplayVOs.size() <= 0) return new ArrayList<>();

        //把DeviceDateDisplayVOs放到对应的组里面去
        List<GroupDeviceDisplayVO> newGroupDeviceDisplayVOS = new ArrayList<>();
        Map<String, List<DeviceDateDisplayVO>> groupBy = deviceDateDisplayVOs.stream().collect(Collectors.groupingBy(DeviceDateDisplayVO::getProcessunitValue));
        groupDeviceDisplayVOS.forEach(item -> {
            List<DeviceDateDisplayVO> deviceDateDisplayVOS = groupBy.get(item.getProcessunitValue());
            if (null != deviceDateDisplayVOS && deviceDateDisplayVOS.size() > 0) {
                item.setDisplayVOList(deviceDateDisplayVOS);
                newGroupDeviceDisplayVOS.add(item);
            }
        });
        return newGroupDeviceDisplayVOS;
    }

    public List<GroupDeviceDisplayVO> findPlcLogicByStationId(Long stationId) throws BizException {
        Asserts.notNull(stationId, "站点id不能为空");
        //厂站和工艺单元
        List<GroupDeviceDisplayVO> groupDeviceDisplayVOS = stationServiceImpl.selectProcessunitByStationId(stationId, null, null);
        if (null == groupDeviceDisplayVOS || groupDeviceDisplayVOS.size() == 0) return Lists.newArrayList();

        //方案二 排除参数设置点位数据
        List<DeviceDateDisplayVO> deviceDateDisplayVOs = stationServiceImpl.findPlcLogicByStationId(stationId, 1);
        if (null == deviceDateDisplayVOs || deviceDateDisplayVOs.size() <= 0) return new ArrayList<>();

        //把DeviceDateDisplayVOs放到对应的组里面去
        List<GroupDeviceDisplayVO> newGroupDeviceDisplayVOS = new ArrayList<>();
        Map<String, List<DeviceDateDisplayVO>> groupBy = deviceDateDisplayVOs.stream().collect(Collectors.groupingBy(DeviceDateDisplayVO::getProcessunitValue));
        groupDeviceDisplayVOS.forEach(item -> {
            List<DeviceDateDisplayVO> deviceDateDisplayVOS = groupBy.get(item.getProcessunitValue());
            if (null != deviceDateDisplayVOS && deviceDateDisplayVOS.size() > 0) {
                item.setDisplayVOList(deviceDateDisplayVOS);
                newGroupDeviceDisplayVOS.add(item);
            }
        });
        return newGroupDeviceDisplayVOS;
    }

    public Object findCameraByStationId(Long stationId) throws BizException {
        StationPO po = stationServiceImpl.findById(stationId);
        return Optional.ofNullable(po).map(p -> p.getCamera()).orElseThrow(() -> new BizException("站点不存在"));
    }

    public List<Integer> selectFactoryPeriodList(List<Long> dutyUserStationIds) {
        if (dutyUserStationIds == null || dutyUserStationIds.size() <= 0) return Lists.newArrayList();
        return gatherDevPointDAO.selectFactoryPeriodList(dutyUserStationIds);
    }

    public Map<String, List<ProcessunitFPVO>> findProcUByStationId(StationIdDTO dto) {
        List<ProcessunitFPVO> procs = gatherDevPointDAO.findProcUByStationId(dto);
        return procs.stream().collect(Collectors.groupingBy(ProcessunitFPVO::getFactoryPeriod));
    }

    public List<DeviceDateDisplayVO> findInQualityByStationId(JCWaterDTO dto) {
        return gatherDevPointDAO.findInQualityByStationId(dto);
    }

    public Double selectCurrOutWater(StationIdDTO dto, String gValue) {
        return gatherDevPointDAO.selectCurrOutWater(dto.getStationId(), gValue);
    }

    public void updateByStationIdAndIotGateway(Long stationId, String netNum) {
        gatherDevPointDAO.updateByStationIdAndIotGateway(stationId, netNum);
    }
}
